#ifndef inicpp_h
#define inicpp_h

#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <exception>
#include <stdexcept>
#include <map>
#include <cctype>

namespace INI
{

	class InvalidIniCPPException : public std::logic_error
	{
		
		public:
			
			explicit InvalidIniCPPException( const std::string & msg ) : std::logic_error( "IniCPP is invalid: " + msg ){}

	};
	
	class InvalidINIFormatException : public std::logic_error
	{
		
		public:
			
			explicit InvalidINIFormatException( const std::string & msg ) : std::logic_error( "Invalid INI format, at line: " + msg ){}

	};
	
	class InvalidValueTypeCastException : public std::logic_error
	{

		public:
		
			explicit InvalidValueTypeCastException( const std::string & msg ) : std::logic_error( "The IniValue was cast to the wrong type: " + msg ){}

	};
	
	enum ValueType
	{
	
		TYPE_NULL,
		TYPE_STRING,
		TYPE_INT,
		TYPE_FLOAT
	
	};
	
	struct IniValue
	{
	
		ValueType type;
		void * value;
	
		IniValue() : type( TYPE_NULL ) {}
		
		IniValue( const IniValue & other )
		{
		
			this->type = other.type;
			this->value = other.value;
		
		}
		
		IniValue( ValueType type_in ) : type( type_in ) {}
		
		IniValue( ValueType type_in, void * val ) : type( type_in ), value( val ) {}
		
		IniValue & operator=( const IniValue other )
		{
			
			this->type = other.type;
			this->value = other.value;
			
			return *this;
		
		}
		
		operator int() const
		{
		
			if( this->type == TYPE_INT )
			{
			
				int * output;
				output = ( int * )( this->value );
				return *output;
			
			}
			else
			{
			
				throw InvalidValueTypeCastException( "Attempt to cast to integer" );
			
			}
		
		}
		
		operator double() const
		{
		
			if( this->type == TYPE_FLOAT )
			{
			
				double * output;
				output = ( double * )( this->value );
				return *output;
			
			}
			else
			{
			
				throw InvalidValueTypeCastException( "Attempt to cast to floating point" );
			
			}
		
		}
		
		operator std::string() const
		{
		
			if( this->type == TYPE_NULL )
			{
			
				return "NULL Value";
			
			}
			else if( this->type == TYPE_STRING )
			{
			
				return *( ( std::string * )this->value );
			
			}
			else if( this->type == TYPE_INT )
			{
			
				std::stringstream stream;
				stream << *( ( int * )( this->value ) );
				
				return stream.str();
			
			}
			else if( this->type == TYPE_FLOAT )
			{
			
				std::stringstream stream;
				stream << *( ( double * )( this->value ) );
				
				return stream.str();
			
			}
			else
			{
			
				return "";
			
			}
		
		}
	
	};
	
	
	// typedef statements are so great!
	typedef std::map< std::string, IniValue > SECTION;

	class IniCPP
	{

		private:
			
			std::string iniFile;
			std::ifstream iniStream_in;
			std::map< std::string, SECTION > sections;
			
			SECTION & parseLine( SECTION & section, std::string line, unsigned int lineNumber );
		
		public:
		
			// Constructor
			IniCPP( std::string iniFile );
			
			// Functionality
			SECTION & operator[]( const std::string & index );
			void readIni();
			void writeIni( std::string outIniFile );
			operator std::string() const;

	};

}

#endif
